﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Cpuss.Strategies.Properties;

namespace Cpuss.Strategies
{

    /// <summary>
    /// Executes the processes in the ready queue using shortest job first with large process priority elevevation.
    /// </summary>
    /// <remarks>
    /// <para>
    /// <em>L</em> = % of large processes.<br />
    /// <em>t</em> = current time.<br />
    /// <em>T</em> = % threshold.<br />
    /// <em>N</em> = % of large processes.
    /// </para>
    /// <para>
    /// iff <em>L<sub>t</sub> > T</em> then invoke the expert rule (execute <em>N<sub>t</sub></em> large processes).
    /// </para>
    /// <para>
    /// By default <em>N</em> = 50%. The threshold can be modified.
    /// </para>
    /// </remarks>
    public class ShortestJobFirstExpert : IStrategy
    {

        private readonly int _pollTime;
        private readonly int _threshold;

        /// <summary>
        /// Initializes a new instance of <see cref="ShortestJobFirstExpert"/>.
        /// </summary>
        /// <param name="pollTime">Poll time.</param>
        /// <param name="threshold">Threshold (%) that when crossed invokes the expert rule.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <para>
        /// <strong>pollTime</strong> is less than <strong>1</strong>
        /// </para>
        /// <para>
        /// -- or --
        /// </para>
        /// <para>
        /// <strong>threshold</strong> is less than <strong>1</strong> or greater than <strong>100</strong>.
        /// </para>
        /// </exception>
        public ShortestJobFirstExpert(int pollTime, int threshold)
        {
            if (pollTime < 1)
            {
                throw new ArgumentOutOfRangeException(Resources.PollTimeGreaterThanZero);
            }
            else if (threshold < 1 || threshold > 100)
            {
                throw new ArgumentOutOfRangeException(Resources.ThresholdOutsideBounds);
            }

            _pollTime = pollTime;
            _threshold = threshold;
        }

        /// <summary>
        /// Executes the processes in <see cref="Runner.ProcessLoad"/> using a modified <see cref="ShortestJobFirst"/> strategy that elevates the priority
        /// of large processes based on a threshold being crossed (please see documentation for more information).
        /// </summary>
        /// <param name="runner">Runner to use as execution enviroment.</param>
        public void Execute(Runner runner)
        {
            List<Process> readyQueue = new List<Process>(runner.ProcessLoad);
            while (readyQueue.Count > 0)
            {
                List<Process> large = readyQueue.Where(x => x.ArrivalTime <= runner.Time && x.BurstTime >= (int)BurstTime.LargeMin).ToList();
                if ((large.Count / (double)readyQueue.Count) * 100 > _threshold)
                {
                    double n = ((double)large.Count / 100) * 50;
                    int count = 0;
                    while (count < n)
                    {
                        Process p = large[count];
                        while (!Process.IsComplete(p))
                        {
                            runner.UtilizeCpu(p);
                        }
                        runner.LogProcessMetrics(p);
                        readyQueue.Remove(p);
                        count++;
                    }
                }
                else
                {
                    try
                    {
                        Process p = readyQueue.Where(x => x.ArrivalTime <= runner.Time).OrderBy(x => x.BurstTime - x.Data.UtilizedCpuTime).First();
                        runner.UtilizeCpu(p, _pollTime);
                        if (Process.IsComplete(p))
                        {
                            runner.LogProcessMetrics(p);
                            readyQueue.Remove(p);
                        }
                    }
                    catch (InvalidOperationException)
                    {
                        runner.SkipIdleCpuTime();
                    }
                }
            }
        }

        /// <summary>
        /// Returns a <see cref="string"/> that represents the current <see cref="object"/>.
        /// </summary>
        /// <returns>String representation of object.</returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "Shortest Job First Expert (Poll Time = {0}, Threshold = {1}%)", _pollTime, _threshold);
        }

    }

}
